package by.yativem.commons.task;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;

import by.yativem.commons.messages.Messages;

public abstract class AbstractManagedTask extends AbstractTask {
	private static final Logger log = Logger.getLogger(AbstractManagedTask.class);

	protected Messages taskMessages;

	protected static final class TaskCanceledException extends Exception {
		public TaskCanceledException(String msg) {
			super(msg);
		}

		private static final long serialVersionUID = 1L;
	}

	public enum TaskStatus {
		NO_STARTED, RUNNING, PAUSED, WAIT, CANCELED, FINISHED_ERROR, FINISHED
	}

	protected final Object locker = new Object();
	protected TaskStatus taskStatus;

	// TODO synchronized
	protected List<AbstractTaskCoomand<AbstractManagedTask>> commands = new ArrayList<AbstractTaskCoomand<AbstractManagedTask>>();// new
																																	// ArrayList<AbstractManagedTask.AbstractTaskCoomand>();
	protected List<AbstractCallableTaskCommand<?, AbstractManagedTask>> callableCommands = new ArrayList<AbstractCallableTaskCommand<?, AbstractManagedTask>>();

	public AbstractManagedTask() {
		taskStatus = TaskStatus.NO_STARTED;
		taskMessages = new Messages();
	}

	public AbstractManagedTask(String name, String description) {
		this();
		this.taskDescription = description;
		this.taskName = name;
	}

	public synchronized void putCommand(AbstractTaskCoomand<AbstractManagedTask> command) {
		command.setTask(this);
		commands.add(command);
	}

	protected void putCallableCommand(AbstractCallableTaskCommand<?, AbstractManagedTask> callableCommand) {

		synchronized (callableCommands) {

			if (this.isFinished()) {
				throw new IllegalStateException("Task finished and could not execute command. " + this.toString());
			}
			// callableCommand.setTask(this);
			if (callableCommand instanceof by.yativem.commons.task.MangetTaskCommand.ContinueTaskCommand) {
				synchronized (locker) {
					this.locker.notifyAll();
				}
			}

			if (callableCommand instanceof by.yativem.commons.task.MangetTaskCommand) {
				callableCommands.add(0, callableCommand);
			} else {
				callableCommands.add(callableCommand);
			}
		}

		synchronized (callableCommand) {
			try {
				callableCommand.wait();
			} catch (InterruptedException e) {
				log.error("Error on execution command of task ", e);
			}
		}
	}

	public void run() {
		startDate = new Date(System.currentTimeMillis());
		taskStatus = TaskStatus.RUNNING;
		try {
			doTask();
		} catch (Exception e) {
			log.error("Exception while running taks. Task stooped. ", e);
			taskStatus = TaskStatus.FINISHED_ERROR;
		} finally {
			endDate = new Date(System.currentTimeMillis());
			if (taskStatus != TaskStatus.FINISHED_ERROR && taskStatus != TaskStatus.CANCELED) {
				taskStatus = TaskStatus.FINISHED;
			}
			notifyAllThreads();
		}
	}

	@Override
	public boolean isFinished() {
		return super.isFinished();
	}

	/*
	 * Check the commands list and execute if exist. Will called in to doTask()
	 * methods;
	 */
	protected synchronized void checkCommands() throws Exception {
		if (!commands.isEmpty()) {
			while (!commands.isEmpty()) {

				try {
					commands.get(0).excequte();
					commands.remove(0);
					if (this.taskStatus == TaskStatus.CANCELED) {
						throw new TaskCanceledException(this.toString() + "Task was canceled");
					}

				} catch (Exception e) {
					if (e instanceof TaskCanceledException)
						throw e;
					log.error("Error on execution command of task ", e);
				}
			}
		}

	}

	protected void checkCallableCommands() throws Exception {

		List<AbstractCallableTaskCommand<?, AbstractManagedTask>> tmpCallableCommands;

		synchronized (callableCommands) {
			if (callableCommands.isEmpty()) {
				return;
			}
			tmpCallableCommands = new ArrayList<AbstractCallableTaskCommand<?, AbstractManagedTask>>(callableCommands);
			callableCommands.clear();

		}
		AbstractCallableTaskCommand<?, AbstractManagedTask> command =null;
		while (!tmpCallableCommands.isEmpty()) {
			try {
				 command = tmpCallableCommands.get(0);				
					tmpCallableCommands.remove(0);
					command.executeInternal();
					if (this.taskStatus == TaskStatus.CANCELED) {
						throw new TaskCanceledException(this.toString() + " Was Canceled");
					}				

			}catch (TaskCanceledException e) {				
				log.error("Task was cancelled", e );
				//skeep exeqution of task commands
				return;
			} 
			catch (Exception e) {
				log.error("Error on execution command of task ", e);
			}finally{
				synchronized (command) {
					command.notifyAll();
				}
			}
		}
	}

	protected synchronized void checkAllCommands() {
		try {
			checkCallableCommands();
			checkCommands();
		} catch (Exception e) {
			log.error("", e);
		}

	}

	private synchronized void notifyAllThreads() {

		for (AbstractCallableTaskCommand<?, AbstractManagedTask> command : callableCommands) {
			try {
				command.notifyAllLockedThreads();
			} catch (Exception e) {
				log.error("", e);
			}
		}
		callableCommands.clear();
	}

	protected synchronized final void setTaskStatus(TaskStatus taskStatus) {
		this.taskStatus = taskStatus;
	}

	public TaskStatus getTaskStatus() {
		return taskStatus;
	}

	protected final void pauseTask() {
		this.setTaskStatus(TaskStatus.PAUSED);
		synchronized (locker) {

			try {
				locker.wait();
			} catch (InterruptedException e) {
				log.error(this.toString(), e);
			}
		}
		this.setTaskStatus(TaskStatus.RUNNING);

	}

	protected final void continueTask() {
		synchronized (locker) {
			locker.notifyAll();
		}
		this.setTaskStatus(TaskStatus.RUNNING);
	}

	protected final void cancelTask() throws TaskCanceledException {
		this.setTaskStatus(TaskStatus.CANCELED);
		throw new TaskCanceledException(this.toString() + " : Canceled");
	}

}
